package com.kgar.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kgar.dao.RecruiterDao;
import com.kgar.dao.ResumeDao;
import com.kgar.dto.*;
import com.kgar.entity.graph.node.JobPositionNode;
import com.kgar.entity.graph.node.RecruiterNode;
import com.kgar.entity.graph.node.ResumeNode;
import com.kgar.entity.graph.relationship.RelevantToRelation;
import com.kgar.entity.pojo.*;
import com.kgar.repository.JobInfoDocRepository;
import com.kgar.repository.RecruiterRepository;
import com.kgar.service.AsyncService;
import com.kgar.service.JobInfoService;
import com.kgar.service.RecruiterService;
import com.kgar.service.ResumeService;
import com.kgar.util.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScriptScoreFunctionBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

@Service
@Slf4j
public class RecruiterServiceImpl extends ServiceImpl<RecruiterDao, Recruiter> implements RecruiterService {

    @Resource
    private AsyncService asyncService;

    @Resource
    private JobInfoService jobInfoService;

    @Resource
    private ElasticsearchRestTemplate esRestTemplate;

    @Resource
    private RecruiterRepository recruiterRepository;


    @Resource
    private JobInfoDocRepository jobInfoDocRepository;

    @Resource
    private ResumeDao resumeDao;

    @Override
    public Result saveBaseInfo(Recruiter recruiter) {
        UserDTO user = UserHolder.getUser();
        recruiter.setUserId(user.getUserId());
        boolean save = saveOrUpdate(recruiter);
        asyncService.createRecruiterNode(recruiter);
        return save ? Result.ok() : Result.fail("保存失败");
    }

    @Override
    public Result saveJob(JobInfo jobInfo) {
        UserDTO user = UserHolder.getUser();
        boolean save = jobInfoService.saveOrUpdate(jobInfo);
        Recruiter one = getById(user.getUserId());
        //保存job信息到neo4j，并设置职位标签并保存到mysql和es中
        asyncService.createOrUpdateJobPositionNode(one, jobInfo);
        return save ? Result.ok() : Result.fail("保存失败！");
    }

    @Override
    public Result removeJobById(Integer jobId) {
        UserDTO user = UserHolder.getUser();
        boolean b = jobInfoService.removeById(jobId);
        //实际上是断开招聘者与职位的关系
        asyncService.deleteJobFromNeo4j(user, jobId);

        return b ? Result.ok() : Result.fail("删除失败！");
    }

    @Override
    public Result updateJob(JobInfo jobInfo) {
        //基础保存jobInfo
        boolean save = jobInfoService.saveOrUpdate(jobInfo);
        //进一步处理职位标签并保存到三个数据库
        asyncService.updateJobInfo2AllDataBase(jobInfo);
        return save ? Result.ok() : Result.fail("修改失败！");
    }

    @Override
    public Result getBaseInfo() {
        UserDTO user = UserHolder.getUser();
        Recruiter one = getById(user.getUserId());
        return one == null ? Result.fail("不存在该招聘者") : Result.ok(one);
    }

    @Override
    public Result getBestResume(Integer jobId) {
        try {
            UserDTO user = UserHolder.getUser();
            JobInfo jobInfo = jobInfoService.getById(jobId);

            Pageable pageable = PageRequest.of(0, 30);

            //treeSet集合直接去重并按分数大小排序
            Set<Resume> resumeScoreSet = new TreeSet<>(Comparator.comparing(Resume::getScore).reversed());

            //如果未填写职位信息，则报错返回
            if (jobInfo == null) {
                return Result.fail("不存在该职位");
            }


            String s = jobInfo.getTags();

            // 创建一个基础查询，查询匹配项
            QueryBuilder baseQuery = QueryBuilders.matchQuery("rawText",s);


            // 使用function_score_query包装基础查询，添加ScriptScoreFunctionBuilder
            SearchHits<ResumeDoc> searchHits = getSearchHits(pageable, baseQuery);


            //获取最大分数
            double maxScore = searchHits.getMaxScore() + (searchHits.getMaxScore() < 100 ? 100 : searchHits.getMaxScore() * 0.1);

            double minScore = 0.1;
            if(searchHits.getTotalHits()>0){
                SearchHit<ResumeDoc> minSearchHit = searchHits.getSearchHit(searchHits.getTotalHits()<30?(int)searchHits.getTotalHits()-1:29);
                //获取最小分数
                minScore = minSearchHit.getScore()-(minSearchHit.getScore()>10?minSearchHit.getScore()*0.1:0);
            }
            //用于存放与某个职位相关的简历
            List<Resume> ResumeList = new ArrayList<>();
            for (SearchHit<ResumeDoc> searchHit : searchHits.getSearchHits()) {
                String resumeId = searchHit.getContent().getResumeId();
                Resume resume1 = resumeDao.selectById(Integer.parseInt(resumeId));
                if(resume1!=null){

                    //将原始Elasticsearch查询分数转换为0-100%的匹配度百分比，并保留相对顺序
                    resume1.setScore(normalizeScore(searchHit.getScore(), minScore, maxScore));
                    resumeScoreSet.add(resume1);

                    ResumeList.add(resume1);
                }
            }

            // 在知识图谱中建立职位与简历之间的关系
            asyncService.createOrUpdateRelationWithJob2Resume(user, jobInfo, ResumeList);

            //取去重并排序后的 分数前三十的数据
            List<Resume> resumeList = resumeScoreSet.stream()
                    .limit(30)
                    .toList();
            return Result.ok(resumeList);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }

    }

    @Override
    public Result getJobs() {
        UserDTO user = UserHolder.getUser();
        Optional<RecruiterNode> byId = recruiterRepository.findById(user.getUserId());
        if (byId.isPresent()) {
            RecruiterNode recruiterNode = byId.get();
            List<JobPositionNode> jobPositionNodes = recruiterNode.getJobPositionNodes();

            List<String> jobIdlist = jobPositionNodes.stream().map(jobPositionNode ->
                    jobPositionNode.getJobPositionId().toString()
            ).toList();
            Iterable<JobInfoDoc> jobInfoDocs = jobInfoDocRepository.findAllById(jobIdlist);
            List<JobInfo> jobInfos = new ArrayList<>();
            for (JobInfoDoc jobInfoDoc : jobInfoDocs) {
                jobInfos.add(BeanUtil.copyProperties(jobInfoDoc, JobInfo.class));
            }

            return Result.ok(jobInfos);
        }
        return Result.fail("没有填写职位");

    }

    @Override
    public Result getGraph() {
        UserDTO user = UserHolder.getUser();
        Optional<RecruiterNode> recruiterNodeOptional = recruiterRepository.findById(user.getUserId());
        if (recruiterNodeOptional.isPresent()) {
            RecruiterNode recruiterNode = recruiterNodeOptional.get();
            log.info("招聘者 " + user.getUserName() + " 已获取知识图谱");
            //将数据处理为节点和边
            Graph graph = handlerData2Graph(recruiterNode);

            return Result.ok(graph);
        }
        return Result.fail("招聘者不存在");
    }


    private Graph handlerData2Graph(RecruiterNode recruiterNode) {
        Graph graph = new Graph();
        Set<Node> nodes = new HashSet<>();
        Set<Edge> edges = new HashSet<>();

        Integer userId = recruiterNode.getRecruitId();

        List<JobPositionNode> jobPositionNodes = recruiterNode.getJobPositionNodes();
        String hiringManagerName = jobPositionNodes.get(0).getHiring_manager();

        //创建招聘者节点
        Node uNode = new Node();
        uNode.setId("Recruiter" + userId.toString());
        uNode.setName(hiringManagerName);
        uNode.setTag("Recruiter");
        uNode.setChildrenNum(jobPositionNodes.size());
        uNode.setIsLeaf(false);
        uNode.setLevel(0);
        uNode.setTags(new ArrayList<>());
        uNode.setInfo(new ArrayList<>());

        nodes.add(uNode);


        //遍历该招聘者的职位节点
        for (JobPositionNode jpNode : jobPositionNodes) {
            Long jpId = jpNode.getJobPositionId();
            List<String> jpInfo = jpNode.getTag();
            List<RelevantToRelation> relevantToRelations = jpNode.getRelevantToRelations();

            //创建职位节点
            Node jNode = new Node();
            jNode.setId("Job" + jpId.toString());
            jNode.setName(jpNode.getTitle());
            jNode.setLevel(0);
            jNode.setTag("Job");
            jNode.setChildrenNum(relevantToRelations.size());
            jNode.setInfo(jpInfo);
            jNode.setIsLeaf(false);
            jNode.setTags(new ArrayList<>());
            jNode.setLink("");
            nodes.add(jNode);

            Edge urEdge = new Edge();
            urEdge.setSource(uNode.getId());
            urEdge.setTarget(jNode.getId());
            urEdge.setLabel("");
            urEdge.setId("");
            edges.add(urEdge);

            relevantToRelations = relevantToRelations.subList(0, Math.min(50,relevantToRelations.size()));
            for (RelevantToRelation relevantToRelation : relevantToRelations) {
                ResumeNode resumeNode = relevantToRelation.getResumeNode();
                Double relevanceScore = relevantToRelation.getRelevanceScore();

                //创建简历节点
                Node rNode = new Node();
                rNode.setId("Resume" + resumeNode.getResumeId().toString());
                rNode.setName(resumeNode.getResumeName());
                rNode.setLevel(2);
                rNode.setTag("Resume");
                rNode.setChildrenNum(2);
                rNode.setInfo(resumeNode.getTags());
                rNode.setLink(resumeNode.getLink());
                rNode.setIsLeaf(true);
                rNode.setTags(Collections.singletonList(jNode.getTag()));
                nodes.add(rNode);

                Edge jrEdge = new Edge();
                jrEdge.setSource(jNode.getId());
                jrEdge.setTarget(rNode.getId());
                jrEdge.setLabel(relevanceScore.toString());
                jrEdge.setId("");
                edges.add(jrEdge);


            }
            Long companyId = jpNode.getCompany().getCompanyId();
            String companyName = jpNode.getCompany().getCompanyName();
            String companyAddress = jpNode.getCompany().getAddress();

            Node cNode = new Node();
            cNode.setId("Company" + companyId.toString());
            cNode.setName(companyName);
            cNode.setLevel(2);
            cNode.setTag("Company");
            cNode.setChildrenNum(0);
            cNode.setInfo(Collections.singletonList(companyAddress));
            cNode.setIsLeaf(true);
            cNode.setLink("");
            cNode.setTags(Collections.singletonList(jNode.getTag()));
            nodes.add(cNode);

            Edge jcEdge = new Edge();
            jcEdge.setSource(jNode.getId());
            jcEdge.setTarget(cNode.getId());
            jcEdge.setLabel("");
            jcEdge.setId("");
            edges.add(jcEdge);


            Long educationId = jpNode.getEducation().getEducationId();
            String educationName = jpNode.getEducation().getEducationName();

            Node eNode = new Node();
            eNode.setId("Education" + educationId.toString());
            eNode.setName(educationName);
            eNode.setLevel(2);
            eNode.setTag("Education");
            eNode.setChildrenNum(0);
            eNode.setInfo(new ArrayList<>());
            eNode.setIsLeaf(true);
            eNode.setLink("");
            eNode.setTags(Collections.singletonList(jNode.getTag()));
            nodes.add(eNode);

            Edge jeEdge = new Edge();
            jeEdge.setSource(jNode.getId());
            jeEdge.setTarget(eNode.getId());
            jeEdge.setLabel("");
            jeEdge.setId("");
            edges.add(jeEdge);
        }

        graph.setNodes(nodes.stream().toList());
        graph.setEdges(edges.stream().toList());
        return graph;
    }


    @NotNull
    private SearchHits<ResumeDoc> getSearchHits(Pageable pageable, QueryBuilder matchAllQueryBuilder) {
        //创建一个Painless脚本，生成一个介于0到1之间的随机数
        Script script = new Script(ScriptType.INLINE, "painless", "Math.random()", Collections.emptyMap());

        //创建一个ScriptScoreFunctionBuilder，它会使用上述脚本来修改得分
        ScriptScoreFunctionBuilder scriptScoreFunction = new ScriptScoreFunctionBuilder(script);

        // 使用function_score_query包装基础查询，添加ScriptScoreFunctionBuilder
        FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(matchAllQueryBuilder, scriptScoreFunction);

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(functionScoreQueryBuilder)
                .withPageable(pageable)
                .build();

        return esRestTemplate.search(query, ResumeDoc.class, IndexCoordinates.of("resume"));
    }

    /**
     * 将原始Elasticsearch查询分数转换为0-100%的匹配度百分比，并保留相对顺序
     *
     * @param rawScore Elasticsearch查询返回的原始分数
     * @param minScore 当前结果集中所有文档的最小分数
     * @param maxScore 当前结果集中所有文档的最大分数
     * @return 百分比形式的匹配度
     */
    private static double normalizeScore(double rawScore, double minScore, double maxScore) {
        if (maxScore == minScore) { // 防止除以零的情况，如果所有文档得分相同，则返回统一值
            return 100.0;
        } else {
            return ((rawScore - minScore) / (maxScore - minScore)) * 100.0;
        }
    }
}
